// Copyright (c) 2010 SuccessFactors, Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
//     * Redistributions of source code must retain the above
//       copyright notice, this list of conditions and the following
//       disclaimer.
//
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials
//       provided with the distribution.
//
//     * Neither the name of the SuccessFactors, Inc. nor the names of
//       its contributors may be used to endorse or promote products
//       derived from this software without specific prior written
//       permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.

package org.owasp.jxt.tag.html;

import org.owasp.jxt.Encoder;
import org.owasp.jxt.JxtExpr;
import org.owasp.jxt.TranslateContext;
import org.owasp.jxt.UrlBuilder;

/**
 * URIAttributeConverter
 *
 * @author Jeffrey Ichnowski
 * @version $Revision: 8 $
 */
final class URIAttributeConverter {
    private static final int LOC_PATH = 0;
    private static final int LOC_KEY = 1;
    private static final int LOC_VALUE = 2;
    private static final int LOC_ANCHOR = 3;

    private static final String[] SPECIALS = {
        "?#", // LOC_PATH
        "&=#", // LOC_KEY
        "&#", // LOC_VALUE
        "", // LOC_ANCHOR
    };

    /** No instances allowed. */
    private URIAttributeConverter() {
    }

    public static void convert(TranslateContext ctx, JxtExpr expr) {
        if (expr.isLiteral()) {
            // The URI expression is a literal, output the expression
            // as pre-escaped text
            ctx.data(
                Encoder.XML.apply(
                    Encoder.URI.apply(expr.getText())));
        } else if (!expr.isConcatenation()) {
            // The entire URI attribute is a single expression.
            // Encode the entire URI.

            // TODO: Some applications may pre-encode their URIs,
            // especially anything with parameters.  The end result is
            // that their URIs will be double encoded, and break.

            ctx.codeln("Encoder.XHTML_URI.apply(out, "+expr+");");
        } else {
            // The URI attribute is a concatenation of expressions.
            // We look for and handle key/value pairs in expressions,
            // and separators as text.  For example:
            // "/${path}?${key}=${value}&amp;${key2}=${value2}#${anchor}"

            int location = LOC_PATH;
            StringBuilder code = new StringBuilder();
            // base ? key = value & key = value
            char token = '?';
            code.append("new "+UrlBuilder.class.getName()+"(");
            String sep = "";
            for (JxtExpr sub : expr.getSubExprs()) {
                if (!sub.isLiteral()) {
                    // the current expression is not a literal, append
                    // it to the output URL.  Contextual-encoding is
                    // provided by the separators already output.
                    code.append(sep)
                        .append('(')
                        .append(sub)
                        .append(')');
                    sep = "+";
                } else {
                    String literal = sub.getText();

                    // if we're in the path
                    //    - and we see '?': close off the UrlBuilder constructor, start a key
                    //    - and we see '#': close off the UrlBuilder constructor, start the anchor
                    // if we're in the key
                    //    - and we see '=': close off the key, start the value
                    //    - and we see '&': close off the key with an empty value, start a key
                    //    - and we see '#': close off the key with an empty value, start the anchor
                    // if we're in the value
                    //    - and we see '&': close off the value, start a key
                    //    - and we wee '#': close off the value, start the anchor
                    // if we're in the anchor
                    //    - append everything to the anchor

                    int start=0;
                    int n = literal.length();
                    for (int i=0 ; i<n ; ++i) {
                        char ch = literal.charAt(i);
                        if (SPECIALS[location].indexOf(ch) != -1) {
                            if (start < i || sep != "+") {
                                code.append(sep).append('\"');
                                Encoder.JAVA_STRING.apply(code, literal.substring(start, i));
                                code.append('\"');
                            }

                            start = i+1;

                            if (location == LOC_KEY) {
                                if (ch != '=') {
                                    // in a key, saw '&' or '#'.  Append
                                    // key with an empty value.
                                    code.append(", \"\")");
                                }
                            } else {
                                // close off the constructor (location ==
                                // LOC_PATH) or the key/value (location == LOC_VALUE)
                                code.append(')');
                            }

                            switch (ch) {
                            case '?':
                            case '&':
                                sep = ".append(";
                                location = LOC_KEY;
                                break;
                            case '#':
                                sep = ".anchor(";
                                location = LOC_ANCHOR;
                                break;
                            case '=':
                                sep = ", ";
                                location = LOC_VALUE;
                                break;
                            default:
                                throw new AssertionError();
                            }
                        }
                    } // for each character

                    // after the literal is processed, if there are
                    // any non-special characters remaining, append
                    // them now.
                    if (start < n) {
                        code.append(sep).append('\"');
                        Encoder.JAVA_STRING.apply(code, literal.substring(start, n));
                        code.append('\"');
                        sep = "+";
                    }

                } // if/else literal
            } // for each sub-expression

            if (location == 1) {
                // in an unterminated key, close it off with an empty value
                code.append(", \"\")");
            } else {
                // otherwise close off the constructor, append, or anchor call.
                code.append(')');
            }

            ctx.codeln("Encoder.XML.apply(out, "+code+");");
        }
    }
} // URIAttributeConverter
